package com.beta.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.framework.service.impl.SuperServiceImpl;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.beta.dto.product.ProductItemQuantityDto;
import com.beta.em.order.OrderStatus;
import com.beta.em.order.TransactionType;
import com.beta.em.transacrion.TransactionResource;
import com.beta.em.transacrion.TransactionSubject;
import com.beta.entity.*;
import com.beta.event.order.OrderEvent;
import com.beta.event.order.OrderPayloadEvent;
import com.beta.mapper.OrderSettleMapper;
import com.beta.pay.yeepay.core.Yeepay;
import com.beta.pay.yeepay.request.DivideRequest;
import com.beta.pay.yeepay.request.RefundRequest;
import com.beta.pay.yeepay.response.RefundResponse;
import com.beta.service.*;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 *
 * OrderSettleQueues 表数据服务层接口实现类
 *
 */
@Service
public class OrderSettleQueuesService extends SuperServiceImpl<OrderSettleMapper, OrderSettleQueues> implements IOrderSettleQueuesService {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private static final String PROCESSING_ORDER_QUEUES_CACHE_KEY = "processing_orders";

    @Autowired
    private IOrdersService ordersService;
    @Autowired
    private IOrderStatusService orderStatusService;
    @Autowired
    private IOrderItemsService orderItemsService;
    @Autowired
    private IPaymentsService paymentsService;
    @Autowired
    private IOltpTxnLogsService oltpTxnLogsService;
    @Autowired
    private IUsersService usersService;
    @Autowired
    private IDateTimeService dateTimeService;
    @Autowired
    private IProductItemsService productItemsService;

    @Autowired
    private Yeepay yeepay;
    @Autowired
    private ITransactionsService transactionsService;
    @Autowired
    private PublishService publishService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Value("${yeepay.divide.callbackUrl}")
    private String yeepayDivideCallbackUrl;

    @Override
    public List<OrderSettleQueues> getOrderSettleQueues(Integer orderId, TransactionType txnType) {
        OrderSettleQueues whereEntity = new OrderSettleQueues()
                .setOrderId(orderId)
                .setTxnType(txnType.getValue());
        List<OrderSettleQueues> orderSettleQueues = baseMapper.selectList(new EntityWrapper<>(whereEntity));

//        // 注意：获取的同时锁住记录
//        if (CollectionUtils.isNotEmpty(orderSettleQueues)) {
//            List<Integer> settleIds = orderSettleQueues.stream().map(OrderSettleQueues::getId).collect(Collectors.toList());
//            return baseMapper.selectBatchIdsForUpdate(settleIds);
//        }

        return orderSettleQueues;
    }

    @Override
    public void createSettleQueues(Orders orders) {
        logger.info("订单结算队列 - 创建队列 <== order:{}", JSON.toJSON(orders));
        List<OrderItems> acceptedItems = orderItemsService.getAcceptedItems(orders.getId());
        if (CollectionUtils.isNotEmpty(acceptedItems)) {
            logger.info("创建大B分账结算队列");
            createDealerSettleQueues(orders, acceptedItems);
        }

        List<OrderItems> refundItems = orderItemsService.getRefundItems(orders.getId());
        if (CollectionUtils.isNotEmpty(refundItems)) {
            logger.info("创建小B退款结算队列");
            createRetailerRefundSettleQueues(orders);
        }
    }

    @Override
    public void deleteSettleQueue(Integer orderId) {
        logger.info("订单结算队列 - 删除队列 <== order:{}", orderId);
        OrderSettleQueues whereEntity = new OrderSettleQueues()
                .setOrderId(orderId);

        // 结算完毕，移除队列
        popFromProcessingQueue(orderId);

        this.deleteSelective(whereEntity);
    }

    private void createRetailerRefundSettleQueues(Orders orders) {
        List<OltpTxnLogs> retailerTxnLogs = oltpTxnLogsService.getUserOrderLogs(orders.getUserId(), orders.getId(), Arrays.asList(
                TransactionType.PAYOUT.getValue(),
                TransactionType.PAY_FEE.getValue(),
                TransactionType.REFUND_AGREE.getValue(),
                TransactionType.COMPLETE.getValue()
        ));
        if (CollectionUtils.isEmpty(retailerTxnLogs)) {
            return ;
        }
        BigDecimal amount = retailerTxnLogs.stream().map(OltpTxnLogs::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            return ;
        }

        Users retailer = usersService.selectById(orders.getUserId());
        createQueue(retailer, orders, TransactionType.REFUND_SETTLED, amount);
    }

    private void createDealerSettleQueues(Orders orders, List<OrderItems> acceptItems) {
        Set<Integer> dealerIds = acceptItems.stream().map((OrderItems orderItems) -> {
            ProductItems productItems = productItemsService.selectById(orderItems.getProductItemId());
            return productItems.getUserId();
        }).filter(Objects::nonNull).collect(Collectors.toSet());

        if (CollectionUtils.isEmpty(dealerIds)) {
            return ;
        }

        dealerIds.forEach((Integer dealerId) -> {
            logger.info("创建大B分账结算队列 <== dealerId:{}, orderId:{}", dealerId, orders.getId());
            createDealerSettleQueue(dealerId, orders);
        });
    }

    private void createDealerSettleQueue(Integer dealerId, Orders orders) {
        List<OltpTxnLogs> dealerTxnLogs = oltpTxnLogsService.getUserOrderLogs(dealerId, orders.getId(), Arrays.asList(
                TransactionType.PAYOUT.getValue(),
                TransactionType.PAY_FEE.getValue(),
                TransactionType.REFUND_AGREE.getValue(),
                TransactionType.COMPLETE.getValue(),
                TransactionType.PLATFORM_FEE.getValue()
        ));
        if (CollectionUtils.isEmpty(dealerTxnLogs)) {
            return ;
        }
        BigDecimal amount = dealerTxnLogs.stream().map(OltpTxnLogs::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            return ;
        }
        Users dealer = usersService.selectById(dealerId);
        createQueue(dealer, orders, TransactionType.SETTLED, amount);
    }

    private void createQueue(Users users, Orders orders, TransactionType txnType, BigDecimal amount) {
        Date dbDate = dateTimeService.toDbDate();
        OrderSettleQueues retailerRefundSettleQueue = new OrderSettleQueues()
                .setUserId(users.getId())
                .setUsername(users.getName())
                .setUserType(users.getUserType())
                .setUserPhone(users.getPhone())
                .setLedgerCode(users.getLedgerCode())
                .setAmount(amount)
                .setOrderId(orders.getId())
                .setOrderCode(orders.getCode())
                .setTxnType(txnType.getValue())
                .setTxnDate(dbDate)
                .setCreatedAt(dbDate)
                .setUpdatedAt(dbDate);
        insertSelective(retailerRefundSettleQueue);
    }

    @Override
    public void executeSettleOrder(Integer orderId) {
        Orders orders = ordersService.validateOrder(orderId);

        if (orderStatusService.isSettled(orders) || !orderStatusService.isComplete(orders)) {
            logger.info("订单结算 - 忽略执行 ==> orderId={}, reason={}", orderId, "订单已结算或订单未完成");
            return ;
        }

        if (redisTemplate.hasKey(orderIdCacheKey(orderId))) {
            logger.info("订单结算 - 订单已经在结算中，忽略执行 ==> orderId={}", orderId);
            return ;
        }

        pushToProcessingQueue(orderId);

        // 退款结算
        List<OrderItems> refundAgreeItems = orderItemsService.getRefundAgreeItems(orders.getId());
        boolean needRefund = CollectionUtils.isNotEmpty(refundAgreeItems);
        if (needRefund) {
            executeRefund(orders, refundAgreeItems);
        }

        // 分账结算
        List<OrderItems> acceptedItems = orderItemsService.getAcceptedItems(orderId);
        boolean needDivide = CollectionUtils.isNotEmpty(acceptedItems);
        if (needDivide) {
            executeDivide(orders, acceptedItems);
        } else {
            // 不需要分账时，状态直接 ==> settled； 否则，执行分账，并由分账回调负责触发订单结算完成事件
            orderStatusService.changeStatus(orderId, OrderStatus.SETTLED, null);
            publishService.publishApplicationEvent(new OrderPayloadEvent(OrderEvent.SETTLED, ordersService.selectById(orderId)));
        }
    }

    private void executeRefund(Orders orders, List<OrderItems> refundAgreeItems) {
        if (CollectionUtils.isEmpty(refundAgreeItems)) {
            return;
        }
        if (existsRefundTransaction(orders)) {
            return;
        }

        // 恢复库存项
        restoreProductItemQuantities(refundAgreeItems);

        List<OrderSettleQueues> refundSettleQueues = getOrderSettleQueues(orders.getId(), TransactionType.REFUND_SETTLED);
        if (CollectionUtils.isEmpty(refundSettleQueues)) {
            return ;
        }

        logger.info("退款结算 <== orderItems: {}", JSON.toJSONString(refundAgreeItems));
        BigDecimal amount = refundSettleQueues.stream()
                .map(settledQueues -> settledQueues.getAmount().abs())
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        if (amount.compareTo(BigDecimal.ZERO) > 0) {
            Payments payments = paymentsService.getPaymentByOrderId(orders.getId());
            RefundRequest refundRequest = new RefundRequest(refundRequestId(orders))
                    .setAmount(amount)
                    .setOrderRequestId(payments.getCode())
                    .setMemo(refundMemo(refundAgreeItems));
            RefundResponse refundResponse = yeepay.refund().refund(refundRequest);

            // 创建退款交易记录
            transactionsService.createRefundTransaction(refundResponse.getRefundExternalId(), orders);
        }
    }

    private void restoreProductItemQuantities(List<OrderItems> orderItemsList) {
        if (CollectionUtils.isEmpty(orderItemsList)) {
            return;
        }
        List<ProductItemQuantityDto> productItemQuantityDtos = orderItemsList.stream().map(
                orderItem -> new ProductItemQuantityDto(
                        orderItem.getProductItemId(),
                        orderItem.getQuantity()
                )).collect(Collectors.toList());
        productItemsService.restoreProductItemQuantityBatch(productItemQuantityDtos);
    }

    private boolean existsRefundTransaction(Orders orders) {
        Transactions whereEntity = new Transactions()
                .setSubject(TransactionSubject.REFUND.getValue())
                .setUserId(orders.getUserId())
                .setResourceId(orders.getId())
                .setResourceType(TransactionResource.ORDER.getValue());
        return transactionsService.selectCount(whereEntity) > 0;
    }

    private String refundMemo(List<OrderItems> refundAgreeItems) {
        String refundMemo = refundAgreeItems.stream()
                .map(orderItemsService::getOrderItemName)
                .collect(Collectors.joining("|"));
        return StringUtils.left(refundMemo, 40);
    }

    private void executeDivide(Orders orders, List<OrderItems> acceptedItems) {
        if (CollectionUtils.isEmpty(acceptedItems)) {
            return;
        }

        Integer orderId = orders.getId();
        List<OrderSettleQueues> orderSettleQueues = getOrderSettleQueues(orderId, TransactionType.SETTLED);
        if (CollectionUtils.isEmpty(orderSettleQueues)) {
            logger.info("分账结算队列为空: 订单已经分账或者分账金额小于O,取消分账 <== order:{}, refundItems:{}", orders.getId(), JSON.toJSON(acceptedItems));
            return;
        }

        logger.info("分账结算 <== orderItems: {}", JSON.toJSONString(acceptedItems));
        List<DivideRequest.DivideInfo> divideInfos = orderSettleQueues.stream().map(
                settledQueues -> DivideRequest.DivideInfo.ofAmount(settledQueues.getLedgerCode(), settledQueues.getAmount().abs())
        ).collect(Collectors.toList());

        Payments payments = paymentsService.getPaymentByOrderId(orderId);
        DivideRequest request = new DivideRequest(divideRequestId(orders))
                .setOrderRequestId(payments.getCode())
                .setDivideInfos(divideInfos)
                .setCallbackUrl(yeepayDivideCallbackUrl);
        yeepay.divide().divide(request);
    }

    private String divideRequestId(Orders orders) {
        return new StringJoiner("-", "D", "")
                .add(orders.getCode())
                .add(String.valueOf(dateTimeService.toDbDate().getTime()))
                .toString();
    }

    private String refundRequestId(Orders orders) {
        return new StringJoiner("-", "R", "")
                .add(orders.getCode())
                .add(String.valueOf(dateTimeService.toDbDate().getTime()))
                .toString();
    }

    @Override
    public void executeSettleAll() {
        // 存在处理中的订单，不执行，避免重复结算
        if (hasProcessingOrder()) {
            return ;
        }

        List<OrderSettleQueues> orderSettleQueues = selectList(null);
        if (CollectionUtils.isEmpty(orderSettleQueues)) {
            return ;
        }

        Set<Integer> orderIds = orderSettleQueues.stream().map(OrderSettleQueues::getOrderId).collect(Collectors.toSet());
        orderIds.forEach(this::executeSettleOrder);
    }

    @Override
    public boolean hasProcessingOrder() {
        Set<String> processingOrders = redisTemplate.keys(PROCESSING_ORDER_QUEUES_CACHE_KEY + ":*");
        return CollectionUtils.isNotEmpty(processingOrders);
    }

    private void pushToProcessingQueue(Integer orderId) {
        redisTemplate.opsForValue().set(orderIdCacheKey(orderId), "process", 2, TimeUnit.SECONDS);
    }

    private void popFromProcessingQueue(Integer orderId) {
        redisTemplate.delete(orderIdCacheKey(orderId));
    }

    private String orderIdCacheKey(Integer orderId) {
        return PROCESSING_ORDER_QUEUES_CACHE_KEY + ":" + orderId;
    }

}